using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Kaleida.ServiceMonitor.Framework
{
    internal class DataSizeParser
    {
        private static readonly IList<DataSizeParser> parsers = new[]
                                                                     {
                                                                         new DataSizeParser("GB", "GB", new[]{"gigabyte", "gigabytes", "gbyte", "gbytes"}, DataSize.FromGigabytes),
                                                                         new DataSizeParser("MB", "MB", new[]{"megabyte", "megabytes", "mbyte", "mbytes"}, DataSize.FromMegabytes),
                                                                         new DataSizeParser("KB", "KB", new[]{"kilobyte", "kilobytes", "kbyte", "kbytes"}, DataSize.FromKilobytes),
                                                                         new DataSizeParser("byte", "bytes", new[]{"b", ""}, DataSize.FromBytes)
                                                                     };

        private readonly IList<Regex> patterns;
        private readonly string canonicalSuffix;
        private readonly string pluralCanonicalSuffix;
        private readonly IEnumerable<string> permittedAlternativeSuffices;
        private readonly Func<double, DataSize> converter;

        public static bool CanParse(string text)
        {
            return parsers.Any(i => i.CanAccept(text));
        }

        public static DataSize ParseDataSize(string text)
        {
            return GetSpecificParser(text).BuildDataSize(text);
        }

        private static DataSizeParser GetSpecificParser(string text)
        {
            var parser = (from i in parsers
                          where i.CanAccept(text)
                          select i).FirstOrDefault();

            if (parser == null)
            {
                var supportedSuffices = parsers.Select(i => i.CanonicalSuffix);
                string validSuffices = string.Join(", ", supportedSuffices.Where(i => i.Trim() != ""));
                throw new FormatException(string.Format("Cannot parse '{0}' as a DataSize. The following suffices may be used: {1}", text, validSuffices));
            }

            return parser;
        }


        public DataSizeParser(string canonicalSuffix, string pluralCanonicalSuffix, IList<string> permittedAlternativeSuffices, Func<double, DataSize> converter)
        {
            this.canonicalSuffix = canonicalSuffix;
            this.pluralCanonicalSuffix = pluralCanonicalSuffix;
            this.permittedAlternativeSuffices = permittedAlternativeSuffices;
            this.converter = converter;

            patterns = SupportedSuffices.Select(i => new Regex(@"^\s*(-?[0-9.,]+)\s*" + i.ToLowerInvariant() + "$", RegexOptions.Compiled)).ToList();
        }

        public bool CanAccept(string text)
        {
            var preparedText = TrimAndLowercase(text);
            return patterns.Any(i=>i.IsMatch(preparedText));
        }

        public DataSize BuildDataSize(string text)
        {
            return converter(GetValue(text));
        }

        public IEnumerable<string> SupportedSuffices
        {
            get { return new[] {canonicalSuffix, pluralCanonicalSuffix}.Concat(permittedAlternativeSuffices); }
        }

        public string CanonicalSuffix
        {
            get { return canonicalSuffix; }
        }

        private double GetValue(string text)
        {
            var preparedText = TrimAndLowercase(text);
            var regex = patterns.FirstOrDefault(i => i.IsMatch(preparedText));
            if(regex == null)
                throw new InvalidOperationException(string.Format("Cannot parse '{0}' with the {1} DataSizeParser", text, canonicalSuffix));

            var match = regex.Match(preparedText);
            if (!match.Success || match.Groups.Count != 2)
                throw new InvalidOperationException(String.Format("Expected '{0}' to product 2 groups using pattern '{1}'", text, regex));

            double value;
            if (!Double.TryParse(match.Groups[1].Value, out value))
                throw new InvalidOperationException(String.Format("Couldn't convert '{0}' to a double", match.Groups[1].Value));
            return value;
        }

        private static string TrimAndLowercase(string text)
        {
            return text.Trim().ToLowerInvariant();
        }
    }
}